using System.Threading.Tasks;
using ZyGame.Transport.Common;
using WebSocketSharp;
using System.Collections.Generic;
using System;

namespace ZyGame.Transport.Web
{
    public sealed class WebSocket : IChannel
    {
        private WebSocketSharp.WebSocket _websocket;
        private string url;
        private INetworkSerialized serialized;
        private List<IChannelHandler> handlers = new List<IChannelHandler>();
        private TaskCompletionSource taskCompletionSource;
        private Dictionary<Type, Action<object>> waitingList = new Dictionary<Type, Action<object>>();
        public bool IsConnected => _websocket == null ? false : _websocket.IsConnected;


        public WebSocket(string url, INetworkSerialized serialized)
        {
            this.url = url;
            this.serialized = serialized;
        }
        public void AddOutput(IChannelHandler handler)
        {
            handlers.Add(handler);
        }

        public void RemoveOutput(IChannelHandler handler)
        {
            handlers.Remove(handler);
        }

        public Task Close()
        {
            _websocket.Close();
            return Task.CompletedTask;
        }

        public Task Connect()
        {
            taskCompletionSource = new TaskCompletionSource();
            _websocket = new WebSocketSharp.WebSocket(url);
            _websocket.OnOpen += OnConnectCompletion;
            _websocket.OnError += OnWebSocketCrashErrord;
            _websocket.OnMessage += OnWebSocketRecvied;
            _websocket.OnClose += OnWebSocketClosed;
            _websocket.Connect();
            Client.Tools.RunningInMainThread(() =>
            {
                for (int i = 0; i < handlers.Count; i++)
                {
                    handlers[i].HandleActived(this);
                }
            });
            return Task.CompletedTask;
        }

        private void OnWebSocketClosed(object sender, CloseEventArgs e)
        {
            Client.Tools.RunningInMainThread(() =>
            {
                for (int i = 0; i < handlers.Count; i++)
                {
                    handlers[i].HandleInactived(this);
                }
            });
        }

        private void OnWebSocketRecvied(object sender, MessageEventArgs e)
        {
            Client.Tools.RunningInMainThread(() =>
            {
                object message = serialized.Deserialized(new ByteBuf(e.RawData));
                // Client.Console.WriteLine($"[WEB SOCKET RECVIED] [{message.GetType().Name}] {Newtonsoft.Json.JsonConvert.SerializeObject(message)}");
                Type type = message.GetType();
                if (waitingList.TryGetValue(type, out Action<object> callback))
                {
                    callback(message);
                    waitingList.Remove(type);
                    return;
                }

                for (int i = 0; i < handlers.Count; i++)
                {
                    handlers[i].HandleReceive(this, message);
                }
            });
        }

        private void OnWebSocketCrashErrord(object sender, ErrorEventArgs e)
        {
            Client.Tools.RunningInMainThread(() =>
            {
                for (int i = 0; i < handlers.Count; i++)
                {
                    handlers[i].HandleException(this, e.Exception);
                }
            });
        }

        private void OnConnectCompletion(object sender, EventArgs e)
        {
            taskCompletionSource.Completion();
            taskCompletionSource = null;
        }

        public Task WriteAndFlush(object message)
        {
            TaskCompletionSource taskCompletionSource = new TaskCompletionSource();
            ByteBuf byteBuf = serialized.Serialized(message);

            _websocket.SendAsync(byteBuf.GetRaw(), state =>
            {
                taskCompletionSource.Completion();
                // Client.Tools.RunningInMainThread(() => Client.Console.WriteLine($"[WEB SOCKET SEND] {Newtonsoft.Json.JsonConvert.SerializeObject(message)}"));
            });
            return taskCompletionSource.Task;
        }

        public Task<T> WriteAndFlush<T>(object meesage)
        {
            TaskCompletionSource<T> taskCompletionSource = new TaskCompletionSource<T>();
            if (waitingList.ContainsKey(typeof(T)))
            {
                waitingList[typeof(T)] += args =>
                {
                    taskCompletionSource.SetResult((T)args);
                };
            }
            else
            {
                waitingList[typeof(T)] = args =>
                {
                    taskCompletionSource.SetResult((T)args);
                };
            }
            WriteAndFlush(meesage);
            return taskCompletionSource.Task;
        }
    }
}